﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Principal;
using NetFrameworkExtensions.Enterprise.Properties;
using System.Security.Authentication;

namespace NetFrameworkExtensions.Enterprise.Security
{
  public abstract class OperatorPrincipal<TPrincipal, TIdentity> :
    PrincipalBase<TPrincipal, TIdentity>,
    IOperatorPrincipal
    where TPrincipal : OperatorPrincipal<TPrincipal, TIdentity>
    where TIdentity : class, IIdentity, new()
  {
    bool isSuperOperator;
    HashSet<IAuthorizableOperation> grantedOperations;

    public OperatorPrincipal(TIdentity identity, bool isSuperOperator, IEnumerable<string> roles = null, IEnumerable<IAuthorizableOperation> grantedOperations = null)
      : base(identity, roles)
    {
      if (!identity.IsAuthenticated && isSuperOperator)
        throw new NotSupportedException(FormattedResources.UnauthorizedUserCannotBeSuperOperator);


      this.isSuperOperator = isSuperOperator;
    }

    public OperatorPrincipal(TIdentity identity, IEnumerable<string> roles = null, IEnumerable<IAuthorizableOperation> grantedOperations = null)
      : base(identity, roles)
    {
      this.grantedOperations = grantedOperations == null ? null : new HashSet<IAuthorizableOperation>(grantedOperations);
    }

    public OperatorPrincipal(TIdentity identity, IEnumerable<IAuthorizableOperation> grantedOperations)
      : base(identity, null)
    {
      this.grantedOperations = grantedOperations == null ? null : new HashSet<IAuthorizableOperation>(grantedOperations);
    }

    public OperatorPrincipal(TIdentity identity, params IAuthorizableOperation[] grantedOperations)
      : this(identity, (IEnumerable<IAuthorizableOperation>)grantedOperations)
    {
    }

    public OperatorPrincipal(TIdentity identity, params string[] roles)
      : this(identity, (IEnumerable<string>)roles)
    {
    }

    public OperatorPrincipal()
      : base()
    {
    }

    public bool IsSuperOperator
    {
      get { return isSuperOperator; }
    }

    public bool HasPermission(IAuthorizableOperation operation)
    {
      if (operation == null) throw new ArgumentNullException("operation");
      if (!Identity.IsAuthenticated) return false;
      if (isSuperOperator) return true;
      if (grantedOperations == null) return false;
      return grantedOperations.Contains(operation);
    }

    public bool HasPermissionToAccess(string entityName)
    {
      if (string.IsNullOrWhiteSpace(entityName))
        throw new ArgumentNullException("entityName");

      if (!Identity.IsAuthenticated) return false;
      if (isSuperOperator) return true;
      if (grantedOperations == null) return false;

      return grantedOperations.Contains(AuthorizableOperations.ReadEntity(entityName)) ||
             grantedOperations.Contains(AuthorizableOperations.UpdateEntity(entityName)) ||
             grantedOperations.Contains(AuthorizableOperations.CreateEntity(entityName)) ||
             grantedOperations.Contains(AuthorizableOperations.DeleteEntity(entityName));
    }

    public bool HasPermissionToModify(string entityName)
    {
      if (string.IsNullOrWhiteSpace(entityName))
        throw new ArgumentNullException("entityName");

      if (!Identity.IsAuthenticated) return false;
      if (isSuperOperator) return true;
      if (grantedOperations == null) return false;

      return grantedOperations.Contains(AuthorizableOperations.UpdateEntity(entityName)) ||
             grantedOperations.Contains(AuthorizableOperations.CreateEntity(entityName)) ||
             grantedOperations.Contains(AuthorizableOperations.DeleteEntity(entityName));
    }

    public bool HasPermissionToExecute(string entityName, string commandName)
    {
      if (string.IsNullOrWhiteSpace(entityName))
        throw new ArgumentNullException("entityName");
      if (string.IsNullOrWhiteSpace(commandName))
        throw new ArgumentNullException("commandName");

      if (!Identity.IsAuthenticated) return false;
      if (isSuperOperator) return true;
      if (grantedOperations == null) return false;

      return grantedOperations.Contains(AuthorizableOperations.ExecuteCommand(entityName, commandName));
    }

    public void CheckPermission(IAuthorizableOperation operation)
    {
      if (!HasPermission(operation))
        throw new AuthenticationException(FormatOperationAuthenticationMessage(operation));
    }

    public void CheckPermissionToAccess(string entityName)
    {
      if (!HasPermissionToAccess(entityName))
        throw new AuthenticationException(FormatOperationAuthenticationMessage(AuthorizableOperations.ReadEntity(entityName)));
    }

    public void CheckPermissionToModify(string entityName)
    {
      if (!HasPermissionToModify(entityName))
        throw new AuthenticationException(FormatOperationAuthenticationMessage(AuthorizableOperations.UpdateEntity(entityName)));
    }

    public void CheckPermissionToExecute(string entityName, string commandName)
    {
      if (!HasPermissionToExecute(entityName, commandName))
        throw new AuthenticationException(FormatOperationAuthenticationMessage(AuthorizableOperations.ExecuteCommand(entityName, commandName)));
    }

    protected virtual string FormatOperationAuthenticationMessage(IAuthorizableOperation operation)
    {
      return FormattedResources.FormatOperationAuthenticationMessage(operation);
    }
  }
}
